import tkinter as tk
from tkinter import simpledialog,messagebox
from PIL import Image, ImageDraw, ImageFont,ImageTk
import random
# 用于比较字符串的相似性
from difflib import SequenceMatcher


# windows 字体库
win = {"宋体":"C:/Windows/Fonts/simsun.ttc",
       "黑体":"C:/Windows/Fonts/simhei.ttf",
       "隶书":"C:/Windows/Fonts/SIMLI.TTF"}
# 汉字特征库（模型）
mode = {}
# 开始统计
def go():
    global win,mode
    size = int(size_var.get())
    font = font_var.get()
    cn = cn_text.get().split()

    for k in cn:
        # 汉字生成图片，RGBA模式有透明度，白色背景
        cs = Image.new('RGBA', (size, size), (255, 255, 255, 0))
        draw = ImageDraw.Draw(cs)
        font_path = win[font]
        font_str = ImageFont.truetype(font_path, size)
        # 黑色字
        draw.text((0, 0), k,fill=(0, 0, 0, 255), font=font_str)
        # 转换为 Tkinter 图像
        photo = ImageTk.PhotoImage(cs)

        # 在 Tkinter 中显示图像
        label = tk.Label(imgs_frame, image=photo)
        # 防止图像被垃圾回收，否则图片不显示
        label.image = photo
        label.pack(side='left')

        # 核心部分：获得图片信息
        data = list(cs.getdata())
        point = {}  # 像素位
        # 逐行
        for i in range(size):
            # 逐列
            for j in range(size):
                id = f"{i}_{j}"
                s = i * size + j
                # 这里使用透明度即可
                if data[s][3] > 0:
                    # 如果有值就记为 1
                    point[id] = 1
                else:
                    # 否则记为 0
                    point[id] = 0

        # 追加到特征库中：汉字=特征
        mode[k] = point
    # 显示数据模型，方便观察
    # print(mode)
    # 开始验证
    test()

# 测试
def test():
    global win,mode
    # 返回用户输入的汉字
    test_str = simpledialog.askstring('输入汉字', '请输入一个汉字用于测试！')
    if not test_str:
        return
    font_size = int(size_var.get())
    font = font_var.get()
    # 随机放大1~10倍，这样同一个汉字的像素位就不同的了。
    zoom = 2 + int(random.random() * 9)
    width = font_size * zoom
    # 开始绘图
    cs = Image.new('RGBA', (width, width), (255, 255, 255, 0))
    draw = ImageDraw.Draw(cs)
    font_path = win[font]
    # 文字大小不变
    font_str = ImageFont.truetype(font_path, font_size)
    # 此处可以改变一下初始坐标位置
    draw.text((10,10), test_str,fill=(0, 0, 0, 255), font=font_str)
    # 转换为 Tkinter 图像
    photo = ImageTk.PhotoImage(cs)
    label = tk.Label(imgs_frame, image=photo)
    # 防止图像被垃圾回收，否则图片不显示
    label.image = photo
    label.pack(side='left')
    
    # 显示位移后的文字特征集合
    data = list(cs.getdata())
    point = {}
    # 逐行
    for i in range(width):
        # 逐列
        for j in range(width):
            id = f"{i}_{j}"
            s = i * width + j
            if data[s][3] > 0:
                point[id] = 1
            else:
                point[id] = 0
    # print(point)
    # 开始遍历汉字图案特征库
    mode_3 = mode_rect(mode, font_size,3)
    # print(mode_3)
    # 当前汉字图案特征库
    str_mode = {test_str: point}
    point_3 = mode_rect(str_mode, width,3)
    # print(point_3)
    # 最简单的方法就是只比对比图案数组所有值是否完全一致，这里是拼接成字符串再比较
    # 当前字符的图案特征数组
    p3_val = []
    for p_i in point_3[test_str]:
        p_is = str(point_3[test_str][p_i].values())
        p3_val.append(p_is)
    p3len = len(p3_val)
    # 特征库中所有字符的图案特征数组
    word = ''  # 对应的字符
    max_val = 0  # 最大值
    for key in mode_3:
        num = 0
        ps = mode_3[key]
        # 当前汉字图案特征数组
        m3_val = []
        for p in ps:
            vals = str(ps[p].values())
            # 当前坐标
            index = len(m3_val)
            if index < p3len and vals == p3_val[index]:
                # 累计命中数：存在并命中
                num += 1
            # 追加到数组中
            m3_val.append(vals)
        sum = len(m3_val)
        # 命中率：命中数÷当前特位总数
        ps_i = num / sum
        # 返回最大命中率和它对应的汉字
        if ps_i > max_val:
            max_val = ps_i
            word = key
    # 显示结果
    messagebox.showinfo('识别结果', f'你输入的这个字很可能是：{word}')

#  图案特征库，边长，图案大小：默认边长为3的正方形
def mode_rect(old_mode,w, rect=3):
    new_mode = {}
    # 遍历模型
    for key in old_mode:
        # 指定汉字的图案特征
        new_mode[key] = {}
        ps = old_mode[key]
        for j in ps:
            if ps[j] == 1:
                new_mode[key][j] = {}
                # 根据坐标名来寻找周围的坐标，注意我们采用的是先行后列命名法：既y_x
                yx = j.split('_')
                # Y坐标轴
                yx_y = int(yx[0])
                # X坐标轴
                yx_x = int(yx[1])
                # 开始Y轴坐标不能小于0
                y_start = yx_y - (rect - 1) // 2
                if y_start < 0:
                    y_start = 0
                y_end = y_start + rect
                # Y轴结束坐标不能大于边长
                if y_end > w:
                    y_end = w
                x_start = yx_x - (rect - 1) // 2
                # 开始X轴坐标不能小于0
                if x_start < 0:
                    x_start = 0
                x_end = x_start + rect
                # X轴结束坐标不能大于边长
                if x_end > w:
                    x_end = w
                # 逐行逐列寻找并组成图案
                for y_i in range(y_start, y_end):
                    # 逐列寻找
                    for x_i in range(x_start, x_end):
                        # 图案坐标名
                        rect_yx = f"{y_i}_{x_i}"
                        if ps[rect_yx] == 1:
                            # 如果存在就获取坐标的值
                            new_mode[key][j][rect_yx] = 1
                        else:
                            # 否则取0（之所以取0 是因为看不见的地方默认为空）
                            new_mode[key][j][rect_yx] = 0
    return new_mode                    
# 比较两个字符串的相似性，
def similarity_ratio(str1, str2):
    # 创建 SequenceMatcher 对象，比较两个字符串
    s = SequenceMatcher(None, str1, str2)
    # 获取相似性比例
    ratio = s.ratio()
    return ratio

# 绘制窗体
root = tk.Tk()
root.title("通过图案对比识别汉字")
size_var = tk.StringVar()
size_var.set("16")
font_var = tk.StringVar()
font_var.set("黑体")
# 输入框
cn_text = tk.Entry(root)
cn_text.insert(0,'人 口 手 金 木 水 火 土 阴 阳')
cn_text.pack()

# 按钮
frame = tk.Frame(root)
frame.pack(pady=10)
tk.Button(frame, text='测试', command=test).pack(side='left')
# 像素大小输入框
tk.Label(frame, text='像素大小：').pack(side='left')
tk.Entry(frame,textvariable=size_var).pack(side='left')
# 字体选择
tk.Label(frame, text='字体：').pack(side='left')
font_menu = tk.OptionMenu(frame, font_var, '宋体', '黑体', '隶书')
font_menu.pack(side='left')
tk.Button(frame, text='开始', command=go).pack(side='left')

# 图片显示区域
imgs_frame = tk.Frame(root)
imgs_frame.pack(pady=10)

root.mainloop()